React Redux v0.2.1 源码学习

前言

这周突然想学习一下状态管理的写法。看看业界是怎么实现的,之前使用过 redux,那就先从 redux 下手吧,但是,一上来就看最新版本的代码,不太适合新手学习,一方面最新版本已经发展n多年了,功能已经非常完善(代码多难懂),另一方面直接看最新的不了解这个工具是怎么设计出来的。于是就打算学习最早的发布版本 v0.2.1

先来说下我认识的一般的状态管理的基本路子:

全局只存在 唯一state,而前端不直接改变 state,而是通过 action 去改变 state

HelloWorld

一个计数器的栗子,目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
counter
├── App.js
├── Counter.js
├── actions
│ ├── CounterActions.js
│ └── index.js
├── constants
│ └── ActionTypes.js
├── dispatcher.js
└── stores
├── CounterStore.js
└── index.js

actions

函数,返回一个带 type 的对象,或者返回一个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import {
INCREMENT_COUNTER,
DECREMENT_COUNTER
} from '../constants/ActionTypes';

export function increment() {
return {
type: INCREMENT_COUNTER
};
}

export function incrementAsync() {
return dispatch => {
setTimeout(() => {
dispatch(increment());
}, 1000);
};
}

export function decrement() {
return {
type: DECREMENT_COUNTER
};
}

store

返回一个函数,参数 state 和 action,当 state 为空时返回初始值,表示初始化。根据 action 的 type 值,进行相应的做法,返回一个新的 state。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import {
INCREMENT_COUNTER,
DECREMENT_COUNTER
} from '../constants/ActionTypes';

const initialState = { counter: 0 };

function incremenent({ counter }) {
return { counter: counter + 1 };
}

function decremenent({ counter }) {
return { counter: counter - 1 };
}

export default function CounterStore(state, action) {
if (!state) {
return initialState;
}

switch (action.type) {
case INCREMENT_COUNTER:
return incremenent(state, action);
case DECREMENT_COUNTER:
return decremenent(state, action);
default:
return state;
}
}

入口 App.js 你会发现 @provides(dispatcher) 这个奇怪的东西,在 React 里面还经常出现,装饰器。

1
2
3
4
5
6
7
8
9
10
11
12
13
import React, { Component } from 'react';
import Counter from './Counter';
import { provides } from 'redux';
import dispatcher from './dispatcher';

@provides(dispatcher)
export default class App extends Component {
render() {
return (
<Counter />
);
}
}

Couter.js,同样,也出现 performs(方法),observes(观察者)等关键字。使用 state 直接使用 this.props 解构赋值即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from 'react';
import { performs, observes } from 'redux';

@performs('increment', 'decrement')
@observes('CounterStore')
export default class Counter {
render() {
const { increment, decrement } = this.props;
return (
<p>
Clicked: {this.props.counter} times
{' '}
<button onClick={() => increment()}>+</button>
{' '}
<button onClick={() => decrement()}>-</button>
</p>
);
}
}

这些关键字是早起 Redux 状态管理的关键,现在的版本应该已经不使用这种方式了。

解析

dispatcher

通过 provides 将 dispatcher 注入到 App 中,其中,dispatcher 是通过 createDispatcher 创建,并调用了 dispatcher.receive(stores, actions) 进行绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import * as stores from './stores/index';
import * as actions from './actions/index';
import { createDispatcher } from 'redux';

const dispatcher =
module.hot && module.hot.data && module.hot.data.dispatcher ||
createDispatcher();

dispatcher.receive(stores, actions);

module.hot.dispose(data => {
data.dispatcher = dispatcher;
});

export default dispatcher;

receive 方法,actionCreator 将 action 进行封装,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Provide a way to receive new stores and actions
function receive(nextStores, nextActionCreators) {
stores = nextStores;
actionCreators = mapValues(nextActionCreators, wrapActionCreator);

// Merge the observers
observers = mapValues(stores,
(store, key) => observers[key] || []
);

// Dispatch to initialize stores
if (currentTransaction) {
updateState(committedState);
currentTransaction.forEach(dispatch);
} else {
dispatch(BOOTSTRAP_STORE);
}
}

action 进行转化返回一个 dispatchAction 函数,如果 action 为函数,则先执行函数,把 dispatchInTransaction 作为参数传入,这样可以在 action 内部使用该函数了,否则使用 dispatchInTransaction 函数调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
// Bind action creator to the dispatcher
function wrapActionCreator(actionCreator) {
return function dispatchAction(...args) {
const action = actionCreator(...args);
if (typeof action === 'function') {
// Async action creator
action(dispatchInTransaction);
} else {
// Sync action creator
dispatchInTransaction(action);
}
};
}

dispatchInTransaction ,执行 dispatch ,计算 nextState,执行 updateState 更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  // Dispatch in the context of current transaction
function dispatchInTransaction(action) {
if (currentTransaction) {
currentTransaction.push(action);
}
dispatch(action);
}

// Reassign the current state on each dispatch
function dispatch(action) {
if (typeof action.type !== 'string') {
throw new Error('Action type must be a string.');
}

const nextState = computeNextState(currentState, action);
updateState(nextState);
}

获取 store,也就是 CounterStore,把参数传入,获取新的 state

1
2
3
4
5
6
// To compute the next state, combine the next states of every store
function computeNextState(state, action) {
return mapValues(stores,
(store, key) => store(state[key], action)
);
}

updateState 实现,计算变化的 changedKeys,执行 emitChange 进行更新。

1
2
3
4
5
6
7
8
9
10
11
12
// Update state and emit change if needed
function updateState(nextState) {
// Swap the state
const previousState = currentState;
currentState = nextState;

// Notify the observers
const changedKeys = Object.keys(currentState).filter(key =>
currentState[key] !== previousState[key]
);
emitChange(changedKeys);
}

emitChange,获取需要通知的 observers,调用通知函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Notify observers about the changed stores
function emitChange(changedKeys) {
if (!changedKeys.length) {
return;
}

// Gather the affected observers
const notifyObservers = [];
changedKeys.forEach(key => {
observers[key].forEach(o => {
if (notifyObservers.indexOf(o) === -1) {
notifyObservers.push(o);
}
});
});

// Emit change
notifyObservers.forEach(o => o());
}

这里可能有点疑问,obersevers 是什么,从哪来?往下看~

observes.js

将 组件进行装饰,构造函数中有一个

this.unobserve = this.context.observeStores(storeKeys, this.handleChange);
context 就是 dispatcher,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import React, { Component, PropTypes } from 'react';
import pick from 'lodash/object/pick';
import identity from 'lodash/utility/identity';

const contextTypes = {
observeStores: PropTypes.func.isRequired
};

export default function connect(...storeKeys) {
let mapState = identity;

// Last argument may be a custom mapState function
const lastIndex = storeKeys.length - 1;
if (typeof storeKeys[lastIndex] === 'function') {
[mapState] = storeKeys.splice(lastIndex, 1);
}

return function (DecoratedComponent) {
const wrappedDisplayName =
DecoratedComponent.displayName ||
DecoratedComponent.name ||
'Component';

return class extends Component {
static displayName = `ReduxObserves(${wrappedDisplayName})`;
static contextTypes = contextTypes;

constructor(props, context) {
super(props, context);
this.handleChange = this.handleChange.bind(this);
this.unobserve = this.context.observeStores(storeKeys, this.handleChange);
}
....

componentWillUnmount() {
this.unobserve();
}

render() {
return (
<DecoratedComponent {...this.props}
{...this.state} />
);
}
};
};
}

dispatcher observeStores 方法,将需要监听的组件传入,以及 onChange 函数,作为回调使用。最后返回一个函数,移除监听,这个也太妙了吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Provide subscription and unsubscription
function observeStores(observedKeys, onChange) {
// Emit the state update
function handleChange() {
onChange(currentState);
}

// Synchronously emit the initial value
handleChange();

// Register the observer for each relevant key
observedKeys.forEach(key =>
observers[key].push(handleChange)
);

// Let it unregister when the time comes
return () => {
observedKeys.forEach(key => {
const index = observers[key].indexOf(handleChange);
observers[key].splice(index, 1);
});
};
}

当计算好 nextState 后,就会调用 observe 的 onChange 方法, onChange 方法也就是 装饰器里面的方法。最后调用自身的 updateState,使用 setState 进行组件更新。而这些 state 作为 props 传入了我们自己的组件,也就可以通过 this.props 拿到。完美~~~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

handleChange(stateFromStores) {
this.currentStateFromStores = pick(stateFromStores, storeKeys);
this.updateState(stateFromStores, this.props);
}

componentWillReceiveProps(nextProps) {
this.updateState(this.currentStateFromStores, nextProps);
}

updateState(stateFromStores, props) {
if (storeKeys.length === 1) {
// Just give it the particular store state for convenience
stateFromStores = stateFromStores[storeKeys[0]];
}

const state = mapState(stateFromStores, props);
if (this.state) {
this.setState(state);
} else {
this.state = state;
}
}

performs 组件

action 绑定到组件,可以通过 this.props ,通过 this.context.getActions() 拿到 actions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import React, { Component, PropTypes } from 'react';
import pick from 'lodash/object/pick';
import identity from 'lodash/utility/identity';

const contextTypes = {
getActions: PropTypes.func.isRequired
};

export default function performs(...actionKeys) {
let mapActions = identity;

// Last argument may be a custom mapState function
const lastIndex = actionKeys.length - 1;
if (typeof actionKeys[lastIndex] === 'function') {
[mapActions] = actionKeys.splice(lastIndex, 1);
}

return function (DecoratedComponent) {
const wrappedDisplayName =
DecoratedComponent.displayName ||
DecoratedComponent.name ||
'Component';

return class extends Component {
static displayName = `ReduxPerforms(${wrappedDisplayName})`;
static contextTypes = contextTypes;

constructor(props, context) {
super(props, context);
this.updateActions(props);
}

componentWillReceiveProps(nextProps) {
this.updateActions(nextProps);
}

updateActions(props) {
this.actions = mapActions(
pick(this.context.getActions(), actionKeys),
props
);
}

render() {
return (
<DecoratedComponent {...this.props}
{...this.actions} />
);
}
};
};
}

到这里就差不多了~
额外收获

Lodash

  • pick

    1
    2
    3
    4
    5
    var object = { 'user': 'fred', 'age': 40 };
    _.pick(object, 'user');
    // => { 'user': 'fred' }
    _.pick(object, _.isString);
    // => { 'user': 'fred' }
  • identity

    1
    2
    3
    function identity(value) {
    return value;
    }
  • mapValues

    1
    2
    3
    4
    _.mapValues({ 'a': 1, 'b': 2 }, function(n) {
    return n * 3;
    });
    // => { 'a': 3, 'b': 6 }

最后

麻雀虽小,却能看透精髓~